//
// SymbolWriterImpl.cs
//
// Author:
//   Lluis Sanchez Gual (lluis@novell.com)
//
// (C) 2005 Novell, Inc.  http://www.novell.com
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#if !NET_CORE

using System;
using System.Collections;
using System.Diagnostics.SymbolStore;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace ILRuntime.Mono.CompilerServices.SymbolWriter
{
    public class SymbolWriterImpl : ISymbolWriter
    {
        MonoSymbolWriter msw;

        int nextLocalIndex;
        int currentToken;
        string methodName;
        Stack namespaceStack = new Stack();
        bool methodOpened;

        Hashtable documents = new Hashtable();

#if !CECIL
        ModuleBuilder mb;
        delegate Guid GetGuidFunc(ModuleBuilder mb);
        GetGuidFunc get_guid_func;

        public SymbolWriterImpl(ModuleBuilder mb)
        {
            this.mb = mb;
        }

        public void Close()
        {
            MethodInfo mi = typeof(ModuleBuilder).GetMethod(
                "Mono_GetGuid",
                BindingFlags.Static | BindingFlags.NonPublic);
            if (mi == null)
                return;

            get_guid_func = (GetGuidFunc)System.Delegate.CreateDelegate(
                typeof(GetGuidFunc), mi);

            msw.WriteSymbolFile(get_guid_func(mb));
        }
#else
		Guid guid;

		public SymbolWriterImpl (Guid guid)
		{
			this.guid = guid;
		}

		public void Close ()
		{
			msw.WriteSymbolFile (guid);
		}
#endif

        public void CloseMethod()
        {
            if (methodOpened)
            {
                methodOpened = false;
                nextLocalIndex = 0;
                msw.CloseMethod();
            }
        }

        public void CloseNamespace()
        {
            namespaceStack.Pop();
            msw.CloseNamespace();
        }

        public void CloseScope(int endOffset)
        {
            msw.CloseScope(endOffset);
        }

        public ISymbolDocumentWriter DefineDocument(
            string url,
            Guid language,
            Guid languageVendor,
            Guid documentType)
        {
            SymbolDocumentWriterImpl doc = (SymbolDocumentWriterImpl)documents[url];
            if (doc == null)
            {
                SourceFileEntry entry = msw.DefineDocument(url);
                CompileUnitEntry comp_unit = msw.DefineCompilationUnit(entry);
                doc = new SymbolDocumentWriterImpl(comp_unit);
                documents[url] = doc;
            }
            return doc;
        }

        public void DefineField(
            SymbolToken parent,
            string name,
            FieldAttributes attributes,
            byte[] signature,
            SymAddressKind addrKind,
            int addr1,
            int addr2,
            int addr3)
        {
        }

        public void DefineGlobalVariable(
            string name,
            FieldAttributes attributes,
            byte[] signature,
            SymAddressKind addrKind,
            int addr1,
            int addr2,
            int addr3)
        {
        }

        public void DefineLocalVariable(
            string name,
            FieldAttributes attributes,
            byte[] signature,
            SymAddressKind addrKind,
            int addr1,
            int addr2,
            int addr3,
            int startOffset,
            int endOffset)
        {
            msw.DefineLocalVariable(nextLocalIndex++, name);
        }

        public void DefineParameter(
            string name,
            ParameterAttributes attributes,
            int sequence,
            SymAddressKind addrKind,
            int addr1,
            int addr2,
            int addr3)
        {
        }

        public void DefineSequencePoints(
            ISymbolDocumentWriter document,
            int[] offsets,
            int[] lines,
            int[] columns,
            int[] endLines,
            int[] endColumns)
        {
            SymbolDocumentWriterImpl doc = (SymbolDocumentWriterImpl)document;
            SourceFileEntry file = doc != null ? doc.Entry.SourceFile : null;

            for (int n = 0; n < offsets.Length; n++)
            {
                if (n > 0 && offsets[n] == offsets[n - 1] && lines[n] == lines[n - 1] && columns[n] == columns[n - 1])
                    continue;
                msw.MarkSequencePoint(offsets[n], file, lines[n], columns[n], false);
            }
        }

        public void Initialize(IntPtr emitter, string filename, bool fFullBuild)
        {
            msw = new MonoSymbolWriter(filename);
        }

        public void OpenMethod(SymbolToken method)
        {
            currentToken = method.GetToken();
        }

        public void OpenNamespace(string name)
        {
            NamespaceInfo n = new NamespaceInfo();
            n.NamespaceID = -1;
            n.Name = name;
            namespaceStack.Push(n);
        }

        public int OpenScope(int startOffset)
        {
            return msw.OpenScope(startOffset);
        }

        public void SetMethodSourceRange(
            ISymbolDocumentWriter startDoc,
            int startLine,
            int startColumn,
            ISymbolDocumentWriter endDoc,
            int endLine,
            int endColumn)
        {
            int nsId = GetCurrentNamespace(startDoc);
            SourceMethodImpl sm = new SourceMethodImpl(methodName, currentToken, nsId);
            msw.OpenMethod(((ICompileUnit)startDoc).Entry, nsId, sm);
            methodOpened = true;
        }

        public void SetScopeRange(int scopeID, int startOffset, int endOffset)
        {
        }

        public void SetSymAttribute(SymbolToken parent, string name, byte[] data)
        {
            // This is a hack! but MonoSymbolWriter needs the method name
            // and ISymbolWriter does not have any method for providing it
            if (name == "__name")
                methodName = System.Text.Encoding.UTF8.GetString(data);
        }

        public void SetUnderlyingWriter(IntPtr underlyingWriter)
        {
        }

        public void SetUserEntryPoint(SymbolToken entryMethod)
        {
        }

        public void UsingNamespace(string fullName)
        {
            if (namespaceStack.Count == 0)
            {
                OpenNamespace("");
            }

            NamespaceInfo ni = (NamespaceInfo)namespaceStack.Peek();
            if (ni.NamespaceID != -1)
            {
                NamespaceInfo old = ni;
                CloseNamespace();
                OpenNamespace(old.Name);
                ni = (NamespaceInfo)namespaceStack.Peek();
                ni.UsingClauses = old.UsingClauses;
            }
            ni.UsingClauses.Add(fullName);
        }

        int GetCurrentNamespace(ISymbolDocumentWriter doc)
        {
            if (namespaceStack.Count == 0)
            {
                OpenNamespace("");
            }

            NamespaceInfo ni = (NamespaceInfo)namespaceStack.Peek();
            if (ni.NamespaceID == -1)
            {
                string[] usings = (string[])ni.UsingClauses.ToArray(typeof(string));

                int parentId = 0;
                if (namespaceStack.Count > 1)
                {
                    namespaceStack.Pop();
                    parentId = ((NamespaceInfo)namespaceStack.Peek()).NamespaceID;
                    namespaceStack.Push(ni);
                }

                ni.NamespaceID = msw.DefineNamespace(ni.Name, ((ICompileUnit)doc).Entry, usings, parentId);
            }
            return ni.NamespaceID;
        }

    }

    class SymbolDocumentWriterImpl : ISymbolDocumentWriter, ISourceFile, ICompileUnit
    {
        CompileUnitEntry comp_unit;

        public SymbolDocumentWriterImpl(CompileUnitEntry comp_unit)
        {
            this.comp_unit = comp_unit;
        }

        public void SetCheckSum(Guid algorithmId, byte[] checkSum)
        {
        }

        public void SetSource(byte[] source)
        {
        }

        SourceFileEntry ISourceFile.Entry
        {
            get { return comp_unit.SourceFile; }
        }

        public CompileUnitEntry Entry
        {
            get { return comp_unit; }
        }
    }

    class SourceMethodImpl : IMethodDef
    {
        string name;
        int token;
        int namespaceID;

        public SourceMethodImpl(string name, int token, int namespaceID)
        {
            this.name = name;
            this.token = token;
            this.namespaceID = namespaceID;
        }

        public string Name
        {
            get { return name; }
        }

        public int NamespaceID
        {
            get { return namespaceID; }
        }

        public int Token
        {
            get { return token; }
        }
    }

    class NamespaceInfo
    {
        public string Name;
        public int NamespaceID;
        public ArrayList UsingClauses = new ArrayList();
    }
}

#endif
